package com.googlecode.jyoutubeuploader.uploader;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Date;

import org.apache.log4j.Logger;

import com.googlecode.jyoutubeuploader.util.StatisticsCalculator;

public class ThrottledFileInputStream extends FileInputStream {

	private static final int MILLISECONDS_TO_SLEEP_PER_PERCENTAGE = 100;
	private int methodCallCount = 0;
	private long totalBytesRead = 0;
	private long fileSizeBytes;
	private final int throttleSpeedKbps;
	private final int reportingIntervalSeconds;
	private Date lastReportTime = new Date();
	private Date startTime;
	private Logger logger;
	private StatisticsCalculator statisticsCalculator;
	
	public ThrottledFileInputStream(File file, int throttleSpeedKbps, int reportingIntervalSeconds) throws FileNotFoundException {
		super(file);
		this.throttleSpeedKbps = throttleSpeedKbps;
		this.reportingIntervalSeconds = reportingIntervalSeconds;
		this.fileSizeBytes = file.length();
	}

	@Override
	public int read(byte[] b, int off, int len) throws IOException {
		checkStartTime();
		updateCounters(b);
		reportStatistics();
		sleepForRequiredPeriod();
		return super.read(b, off, len);
	}

	protected void reportStatistics() {
		if (reportingIntervalSeconds <= 0){
			// A zero or negative reporting interval indicates no reporting
			return;
		}
		int secondsSinceLastReport = statisticsCalculator.getSecondSinceLastReport(lastReportTime);
		if (secondsSinceLastReport >= reportingIntervalSeconds){
			logDebugStatistics();
			logInfoStatistics();
			lastReportTime = new Date();
		}
	}

	private void sleepForRequiredPeriod() {
		if (throttleSpeedKbps <= 0){
			// A zero or negative throttle speed indicates unlimited (no limit)
			return;
		}
		try {
			float estimatedSpeed = statisticsCalculator.getEstimatedSpeed(
					statisticsCalculator.getElapsedSeconds(startTime), totalBytesRead);
			if (estimatedSpeed > throttleSpeedKbps){
				int percentageOverSpeed = statisticsCalculator.getPercentageOverSpeed(estimatedSpeed, throttleSpeedKbps);
				long calculatedSleepPeriod = percentageOverSpeed * MILLISECONDS_TO_SLEEP_PER_PERCENTAGE;
				long moderatedSleepPeriod = calculatedSleepPeriod > 1000 ? 1000 : calculatedSleepPeriod;
				logger.debug("Calculated sleep period is " + calculatedSleepPeriod + "ms but actually sleeping for " 
						+ moderatedSleepPeriod + "ms due to over speed percentage of " + percentageOverSpeed + "%.");
				Thread.sleep(moderatedSleepPeriod);
			}
		} catch (InterruptedException e) {
			logger.warn("Sleep was interrupted!");
		}
	}

	protected void updateCounters(byte[] b) {
		methodCallCount++;
		totalBytesRead += b.length;
	}

	protected void logInfoStatistics() {
		logger.info("Progress: " + String.format("%1$.1f", statisticsCalculator.getCompletedPercentage(totalBytesRead, fileSizeBytes)) + "%. "
				+ "Estimated speed: " + String.format("%1$.1f", statisticsCalculator.getEstimatedSpeed(statisticsCalculator.getElapsedSeconds(startTime), totalBytesRead)) + "KB/s."
		);
	}

	protected void logDebugStatistics() {
		logger.debug(
				"Method call count: " + methodCallCount + ". "
				+ "Total time elapsed: " + statisticsCalculator.getElapsedSeconds(startTime) + " seconds. "
				+ "Total bytes read: " + totalBytesRead + "bytes. ");
	}

	protected void checkStartTime() {
		if (methodCallCount == 0){
			startTime = new Date();
			logger.debug("File upload start time is " + startTime);
		}
	}

	public void setLogger(Logger logger) {
		this.logger = logger;
	}

	public void setStatisticsCalculator(StatisticsCalculator statisticsCalculator) {
		this.statisticsCalculator = statisticsCalculator;
	}

}
